package com.cloudfun.campusshare.common.security;

import com.alibaba.fastjson.JSONObject;
import com.cloudfun.campusshare.common.constant.BaseConstant;
import com.cloudfun.campusshare.common.constant.Code;
import com.cloudfun.campusshare.common.exception.AuthenticationException;
import com.cloudfun.campusshare.common.model.AccessToken;
import com.cloudfun.campusshare.common.model.Token;
import com.cloudfun.campusshare.common.response.BaseResultFactory;
import com.cloudfun.campusshare.entity.AppEntity;
import com.cloudfun.campusshare.entity.UserEntity;
import com.cloudfun.campusshare.repo.AppEntityRepo;
import com.cloudfun.campusshare.repo.UserEntityRepo;
import com.cloudfun.campusshare.service.RoleService;
import com.cloudfun.campusshare.service.TokenFactory;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.file.AccessDeniedException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Created by Huqin on 2019/12/23
 */
@Slf4j
@Component
public class AuthenticationFilter implements Filter {

    @Autowired
    private TokenFactory tokenFactory;

    @Autowired
    private UserEntityRepo userEntityRepo;

    @Autowired
    private AppEntityRepo appEntityRepo;

    @Autowired
    private RoleService roleService;

    private final List<AntPathRequestMatcher> ignoreUrlMatcher = Stream.of("/v1/session/**", "/v1/app/token", "/v1/user/register", "/v1/user/resetByCode", "/v1/code/**", "/v1/wechat/notify/**",
            "/hsq/**","/v1/kjt/notify")
            .map(AntPathRequestMatcher::new).collect(Collectors.toList());

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
        // 忽略接口
        if (ignoreUrlMatcher.stream().anyMatch(antPathRequestMatcher -> antPathRequestMatcher.matches((HttpServletRequest) servletRequest))) {
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }
        try {
            try {
                String userTokenStr = httpServletRequest.getHeader(BaseConstant.X_ACCESS_TOKEN);
                this.parseUserAuthToken(userTokenStr);
                String appTokenStr = httpServletRequest.getHeader(BaseConstant.T_ACCESS_TOKEN);
                this.parseAppAuthToken(appTokenStr);
            } catch (ExpiredJwtException e) {
                log.error("jwt时间过期：", e);
                httpServletResponse.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                try (ServletOutputStream servletOutputStream = httpServletResponse.getOutputStream()) {
                    servletOutputStream.write(JSONObject.toJSONString(BaseResultFactory.produceResult(Code.TOKEN_INVALID, "token过期")).getBytes());
                    servletOutputStream.flush();
                }
                return;
            } catch (AuthenticationException authException) {
                log.error("认证异常：", authException);
                httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return;
            }
            try {
                filterChain.doFilter(servletRequest, servletResponse);
            } catch (AuthenticationException authException) {
                log.error("认证异常：", authException);
                httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            } catch (AccessDeniedException accessDeniedException) {
                log.error("鉴权异常：", accessDeniedException);
                httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
                servletResponse.flushBuffer();
            }
        } finally {
            AuthenticationHolder.clear();
        }
    }

    private void parseAppAuthToken(String appTokenStr) {
        if (StringUtils.isEmpty(appTokenStr)) {
            return;
        }
        AccessToken accessToken = tokenFactory.verifyAccessStr(appTokenStr);
        AppEntity appEntity = appEntityRepo.findByAppKey(accessToken.getAppKey());
        if (null != appEntity) {
            AuthenticationHolder.setAppAuth(new AppAuthentication(appEntity.getUserId(), appEntity.getId(), appEntity.getAppKey()));
        }
        appEntity = appEntityRepo.findByProdAppKey(accessToken.getAppKey());
        if (null != appEntity) {
            AuthenticationHolder.setAppAuth(new AppAuthentication(appEntity.getUserId(), appEntity.getId(), appEntity.getProdAppKey()));
        }
    }

    private void parseUserAuthToken(String userTokenStr) {
        if (StringUtils.isEmpty(userTokenStr)) {
            return;
        }

        Token token = tokenFactory.verifyJwt(userTokenStr);
        Optional<UserEntity> userOpt = userEntityRepo.findById(token.getUserId());
        if (!userOpt.isPresent()) {
            throw new AuthenticationException("token非法");
        }
        UserEntity userEntity = userOpt.get();
        UserAuthentication userAuthentication = new UserAuthentication(userEntity.getId(), userEntity.getUsername());
        roleService.fillUserAuthentication(userAuthentication);
        AuthenticationHolder.setUserAuth(userAuthentication);
    }
}
